﻿// Copyright 2009 Mike Geise
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace Falcon.Forums.Services
{
	using System;
	using System.Collections.Generic;
	using System.Linq;
	using System.Web;
	using Falcon.Core.Domain;
	using Falcon.Core.Domain.Services;
	using Falcon.Core.Extensions;
	using Falcon.Forums.Domain;
	using Falcon.Forums.Domain.Repositories;
	using Falcon.Forums.Domain.Services;
	using Falcon.Forums.Domain.Values;

	public class ForumService : IForumService
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="ForumService"/> class.
		/// </summary>
		/// <param name="userRoleService">The user role service.</param>
		/// <param name="forumPermissionService">The forum permission service.</param>
		/// <param name="forumAnnouncementService">The forum announcement service.</param>
		/// <param name="forumRepository">The forum repository.</param>
		public ForumService(
			IUserRoleService userRoleService, 
			IForumPermissionService forumPermissionService, 
			IForumAnnouncementService forumAnnouncementService,
			IForumRepository forumRepository)
		{
			this.UserRoleService = userRoleService;
			this.ForumPermissionService = forumPermissionService;
			this.ForumAnnouncementService = forumAnnouncementService;
			this.ForumRepository = forumRepository;
		}

		/// <summary>
		/// Gets or sets the user role service.
		/// </summary>
		/// <value>The user role service.</value>
		public IUserRoleService UserRoleService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum permission service.
		/// </summary>
		/// <value>The forum permission service.</value>
		public IForumPermissionService ForumPermissionService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum announcement service.
		/// </summary>
		/// <value>The forum announcement service.</value>
		public IForumAnnouncementService ForumAnnouncementService
		{
			get;
			private set;
		}

		/// <summary>
		/// Gets or sets the forum repository.
		/// </summary>
		/// <value>The forum repository.</value>
		public IForumRepository ForumRepository
		{
			get; 
			private set;
		}

		/// <summary>
		/// Gets the forum by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public Forum GetBy(ForumSpecification specification)
		{
			// get forum
			var forum = this.ForumRepository.GetBy(specification);

			// get all permissions
			var permissions = this.ForumPermissionService.GetAll();

			// add permissions to forum
			forum.Permissions = permissions.Where(c => c.ForumId == forum.Id).ToList();

			// get all the announcements
			forum.Announcements = this.ForumAnnouncementService.GetAll(new ForumAnnouncementSpecification
			{
				ForumId = forum.Id,
				IsDateRestrictionEnabled = true
			});

			// add any childern to forum
			Childern(forum, specification, permissions);

			return forum;
		}

		/// <summary>
		/// Gets the forum by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <returns></returns>
		public Forum GetById(int id)
		{
			return this.GetById(id, false);
		}

		/// <summary>
		/// Gets the forum by id.
		/// </summary>
		/// <param name="id">The id.</param>
		/// <param name="skipPermissions">if set to <c>true</c> [skip permissions].</param>
		/// <returns></returns>
		public Forum GetById(int id, bool skipPermissions)
		{
			// get forum
			var forum = this.ForumRepository.GetById(id);

			// get all permissions
			var permissions = this.ForumPermissionService.GetAll();

			// add permissions to forum
			forum.Permissions = permissions.Where(c => c.ForumId == forum.Id).ToList();

			// get all the announcements
			forum.Announcements = this.ForumAnnouncementService.GetAll(new ForumAnnouncementSpecification
			{
				ForumId = forum.Id,
				IsDateRestrictionEnabled = true
			});

			// add any childern to forum
			Childern(forum, new ForumSpecification { SkipPermissions = skipPermissions }, permissions);

			return forum;
		}

		/// <summary>
		/// Gets all the forums.
		/// </summary>
		/// <returns></returns>
		public IList<Forum> GetAll()
		{
			return this.GetAll(new ForumSpecification());
		}

		/// <summary>
		/// Gets all the forums by the specified specification.
		/// </summary>
		/// <param name="specification">The specification.</param>
		/// <returns></returns>
		public IList<Forum> GetAll(ForumSpecification specification)
		{
			// get all forums
			var forums = this.ForumRepository.GetAll(specification);

			// get all permissions
			var permissions = this.ForumPermissionService.GetAll();

			// add permissions to forums
			forums.ForEach(x => x.Permissions = permissions.Where(c => c.ForumId == x.Id).ToList());

			// results
			var results = new List<Forum>();

			foreach (var forum in forums)
			{
				if (forum.ParentId <= 0 && (specification.SkipPermissions || forum.CanViewForum))
				{
					results.Add(forum);
				}

				// add any childern to forum
				Childern(forum, specification, forums, forum.Depth);
			}

			return results;
		}

		/// <summary>
		/// Creates the forum.
		/// </summary>
		/// <returns></returns>
		public Forum Create()
		{
			var forum = new Forum
			{
				Created = DateTime.Now
			};

			forum.Updated = forum.Created;

			return forum;
		}

		/// <summary>
		/// Inserts the specified forum.
		/// </summary>
		/// <param name="forum">The <see cref="Forum"/>.</param>
		public void Insert(Forum forum)
		{
			forum.Validate();

			forum.Slug = forum.Title.Slug();
			forum.BodyParsed = forum.Body.BBCodeToHtml(true);
			forum.Order = 0;
			forum.Total = new ForumTotal { Topics = 0, Posts = 0 };

			this.ForumRepository.Insert(forum);

			var permissions = ForumPermissionService.GetDefault();

			foreach (var perm in permissions)
			{
				var role = UserRoleService.GetBy(new UserRoleSpecification { Title = perm.Role.Title });

				if (role == null)
				{
					continue;
				}

				var permission = ForumPermissionService.Create();

				permission.ForumId = forum.Id;
				permission.Role = role;
				permission.Action = perm.Action;
				permission.PostAction = perm.PostAction;
				permission.TopicAction = perm.TopicAction;

				ForumPermissionService.Insert(permission);
			}
		}

		/// <summary>
		/// Inserts the specified forum.
		/// </summary>
		/// <param name="forum">The <see cref="Forum"/>.</param>
		/// <param name="value">The <see cref="ForumCreate"/>.</param>
		public void Insert(Forum forum, ForumCreate value)
		{
			value.Validate();

			forum.ParentId = value.ParentId;
			forum.Title = value.Title;
			forum.Body = value.Body;
			forum.Scope = value.Scope;
			forum.Status = value.Status;

			this.Insert(forum);
		}

		/// <summary>
		/// Updates the specified forum.
		/// </summary>
		/// <param name="forum">The <see cref="Forum"/>.</param>
		public void Update(Forum forum)
		{
			forum.Validate();

			forum.Slug = forum.Title.Slug();
			forum.BodyParsed = forum.Body.BBCodeToHtml(true);
			forum.Updated = DateTime.Now;

			this.ForumRepository.Update(forum);
		}

		/// <summary>
		/// Updates the specified forum.
		/// </summary>
		/// <param name="forum">The <see cref="Forum"/>.</param>
		/// <param name="value">The <see cref="ForumUpdate"/>.</param>
		public void Update(Forum forum, ForumUpdate value)
		{
			value.Validate();

			forum.ParentId = value.ParentId;
			forum.Title = value.Title;
			forum.Body = value.Body;
			forum.Scope = value.Scope;
			forum.Status = value.Status;

			this.Update(forum);
		}

		/// <summary>
		/// Updates the forums order.
		/// </summary>
		/// <param name="values">The values.</param>
		public void Update(ForumUpdateOrder[] values)
		{
			if (values == null)
			{
				return;
			}

			for (var i = 0; i < values.Length; i++)
			{
				if (values[i].Id == 0)
				{
					continue;
				}

				var forum = this.GetById(values[i].Id);

				if (forum == null)
				{
					continue;
				}

				forum.Order = values[i].Order;

				this.Update(forum);
			}
		}

		/// <summary>
		/// Deletes the specified forum.
		/// </summary>
		/// <param name="forum">The <see cref="Forum"/>.</param>
		public void Delete(Forum forum)
		{
			this.ForumRepository.Delete(forum);
		}

		/// <summary>
		/// Marks the forum as read.
		/// </summary>
		/// <param name="forum">The forum.</param>
		public void MarkRead(Forum forum)
		{
			var date = DateTime.Now;
			var name = string.Concat("forum_read_", forum.Id);
			var cookie = new HttpCookie(name) { Value = date.ToString(), Expires = date.AddDays(7) };

			HttpContext.Current.Response.SetCookie(cookie);
		}

		private void Childern(Forum forum, ForumSpecification specification, IEnumerable<ForumPermission> permissions)
		{
			// get all childern
			var forums = this.ForumRepository.GetChilds(new ForumSpecification { ParentId = forum.Id });

			// add permissions to forums
			forums.ForEach(x => x.Permissions = permissions.Where(c => c.ForumId == x.Id).ToList());

			foreach (var result in forums)
			{
				if (forum.Id.Equals(result.ParentId) && (specification.SkipPermissions || result.CanViewForum))
				{
					if (forum.Childern == null)
					{
						forum.Childern = new List<Forum>();
					}

					forum.Childern.Add(result);
				}

				// any any childern
				Childern(result, specification, forums, result.Depth);
			}
		}

		private static void Childern(Forum parent, ForumSpecification specification, IEnumerable<Forum> forums, int depth)
		{
			foreach (var forum in forums)
			{
				if ((!parent.Id.Equals(forum.ParentId)))
				{
					continue;
				}

				if (!specification.SkipPermissions && !forum.CanViewForum)
				{
					continue;
				}

				forum.Depth = depth == 0 ? 1 : depth + 1;

				if (parent.Childern == null)
				{
					parent.Childern = new List<Forum>();
				}

				parent.Childern.Add(forum);
			}
		}
	}
}